package com.github.freewu.script.js;

import android.content.res.AssetManager;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;

import com.github.freewu.commons.http.HttpCallBack;
import com.github.freewu.commons.http.HttpUtils;
import com.github.freewu.script.ScriptContext;
import com.github.freewu.script.ScriptExecCallback;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ScriptableObject;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import okhttp3.Call;
import okio.BufferedSource;
import okio.Okio;

/**
 * js运行环境
 * Created by free on 2017/1/16 0016.
 */
public class RhinoContext implements ScriptContext, Runnable {
    private Context context;

    private ScriptableObject scope;

    //需要运行的脚本
    private List<String> scriptList;

    //需要绑定的java对象
    private Map<String, Object> protoMap;

    //线程计数器
    private AtomicInteger index=new AtomicInteger(0);
    private AtomicBoolean isExec=new AtomicBoolean(false);

    //回调
    private ScriptExecCallback scriptExecCallback;

    private Handler handler=new Handler(Looper.getMainLooper());

    public RhinoContext() {
        scriptList = new ArrayList<>(2);
        protoMap = new HashMap<>();
    }

    @Override
    public RhinoContext addGlobalVariable(String name, Object value) {
        protoMap.put(name, value);
        return this;
    }

    @NonNull
    @Override
    public RhinoContext addScript(String script) {
        scriptList.add(script);
        return this;
    }
    public RhinoContext addScript(final AssetManager manager, final String path){
        index.addAndGet(1);

        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                try {
                    BufferedSource source= Okio.buffer(Okio.source(manager.open(path,AssetManager.ACCESS_BUFFER)));

                    scriptList.add(source.readUtf8());

                    index.addAndGet(-1);
                } catch (IOException e) {
                    e.printStackTrace();

                    index.addAndGet(-1);

                    Log.w(this.getClass().getSimpleName(),"加载脚本失败");
                }finally {
                    if (isExec.get() && index.get()==0){
                        Thread thread = new Thread(RhinoContext.this);
                        thread.start();
                    }
                }
            }
        };

        Thread thread=new Thread(runnable);
        thread.start();

        return this;
    }

    public RhinoContext addScript(URL url){
        index.addAndGet(1);

        HttpUtils.getInstance().get(url.toString(), new HttpCallBack() {
            @Override
            public void onError(Call call,Exception e,int errorCode) {
                index.addAndGet(-1);
            }

            @Override
            public void onSuccess(Call call, String body) {
                index.addAndGet(-1);

                scriptList.add(body);

                if (isExec.get() && index.get()==0){
                    Thread thread = new Thread(RhinoContext.this);
                    thread.start();
                }
            }
        });

        return this;
    }

    /**
     * 执行脚本
     * @return
     */
    @Override
    public ScriptContext execute() {
        if (index.get()==0) {
            Thread thread = new Thread(this);
            thread.start();
        }

        isExec.set(true);

        return this;
    }

    @Override
    public void run() {
        context = Context.enter();

        context.setOptimizationLevel(-1);

        scope = context.initStandardObjects();

        for (String key : protoMap.keySet()) {
            Object value = protoMap.get(key);

            ScriptableObject.putProperty(scope, key, value);
        }

        try {
            for (String script : scriptList) {
                context.evaluateString(scope, script, "RhinoContext", 1, null);
            }
        }catch (final Exception e){
            e.printStackTrace();

            if (scriptExecCallback!=null){
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        scriptExecCallback.onError(e);
                    }
                });
            }
        }

        Context.exit();

        if (scriptExecCallback!=null){
            handler.post(new Runnable() {
                @Override
                public void run() {
                    scriptExecCallback.onSuccess();
                }
            });
        }
    }

    public RhinoContext setScriptExecCallback(ScriptExecCallback scriptExecCallback) {
        this.scriptExecCallback = scriptExecCallback;

        return this;
    }
}
